/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "system/fs/fs.h"
#include "vm.h"

#define JLFS_SECTOR_SIZE    4096
#define __SECTOR_ALIGN(len)   (((len) + JLFS_SECTOR_SIZE - 1) / JLFS_SECTOR_SIZE * JLFS_SECTOR_SIZE)
#define JL_FILE_ATTR_UPDATE   0x04

struct jlfs_file_head {
    u16 head_crc;
    u16 data_crc;
    u32 addr;
    u32 len;
    u8 attr;
    u8 res;
    u16 index;
    char name[16];
};

struct vm_update_info_t {
    char *loader_name;
    u32 reserve_area;
};

static struct vm_update_info_t vm_update_info = {
    .loader_name = "mnt/sdfile/C/LOADER.BIN",
    .reserve_area = 0x200,
};

/* ---------------------------------------------------------------------------- */
/**@brief  VM升级区域整理
   @param  *vm_saddr[out]：WIFI_STATE_AVALIABLE整理后的vm数据起始地址
   @param  *reserve_saddr[out]：整理后的保留区起始地址
   @param  loader_len[in]： WIFI_STATE_AVALIABLE升级文件大小
   @param  new_vm_saddr[in]： 升级之后vm的起始地址，即资源结束地址
   @return
   @note   整理后会在vm区域生成一个长度为loader_len，名字为loader_name的升级文件
   @note   整理后的vm将不可用，需要恢复
   @note   数据恢复，把vm_saddr至reserve_saddr的数据copy到新的vm区起始位置，剩余的vm空间擦除掉
*/
/* ---------------------------------------------------------------------------- */
void vm_update_set_info(u32 reserve_area, char *loader_name)
{
    vm_update_info.reserve_area = reserve_area;
    vm_update_info.loader_name = loader_name;
}

/* ---------------------------------------------------------------------------- */
/**@brief  VM升级区域整理
   @param  *vm_saddr[out]： WIFI_STATE_AVALIABLE整理后的vm数据起始地址
   @param  *reserve_saddr[out]：整理后的保留区起始地址
   @param  loader_len[in]： WIFI_STATE_AVALIABLE升级文件大小
   @param  new_vm_saddr[in]： 升级之后vm的起始地址，即资源结束地址
   @return
   @note   整理后会在vm区域生成一个长度为loader_len，名字为loader_name的升级文件
   @note   整理后的vm将不可用，需要恢复
   @note   数据恢复，把vm_saddr至reserve_saddr的数据copy到新的vm区起始位置，剩余的vm空间擦除掉
*/
/* ---------------------------------------------------------------------------- */
VM_ERR vm_update_defrag(u32 *vm_saddr, u32 *reserve_saddr, u32 loader_len, u32 new_vm_saddr)
{
    VM_ERR ret = VM_ERR_NONE;

    if (vm_init_check() == FALSE) {
        return VM_NOT_INIT;
    }

    // check it
    vm_erase_check(1);

    vm_mutex_enter();

    vm_info_log("saddrA:0x%x, saddrB:0x%x, lastaddr:0x%x \n", VM_AREA_ADR(AREA_A), VM_AREA_ADR(AREA_B), VM_LAST_ADR);

    // defrag
    _vm_defrag(1);

    vm_info_log("last000: 0x%x \n", VM_LAST_ADR);
    u32 res_vm_len;
    if (VM_LAST_ADR > VM_AREA_ADR(AREA_B)) {
        res_vm_len = VM_LAST_ADR - VM_AREA_ADR(AREA_B);
    } else {
        res_vm_len = VM_LAST_ADR - VM_AREA_ADR(AREA_A);
    }
    res_vm_len += vm_update_info.reserve_area;
    vm_info_log("total len :%d \n", res_vm_len);
    if ((__SECTOR_ALIGN(res_vm_len) +
        __SECTOR_ALIGN(loader_len + sizeof(struct jlfs_file_head))) > (VM_AREA_LEN(AREA_A) + VM_AREA_LEN(AREA_B))) {
        vm_err_log("totallen:%d, vmlen:%d\n", (__SECTOR_ALIGN(res_vm_len) + __SECTOR_ALIGN(loader_len +
            sizeof(struct jlfs_file_head))), (VM_AREA_LEN(AREA_A) + VM_AREA_LEN(AREA_B)));
        vm_mutex_exit();
        return -1;
    }
    if (new_vm_saddr) {
        if ((__SECTOR_ALIGN(new_vm_saddr) + __SECTOR_ALIGN(res_vm_len) + __SECTOR_ALIGN(loader_len +
            sizeof(struct jlfs_file_head)))
            > (VM_AREA_ADR(AREA_A) + VM_AREA_LEN(AREA_A) + VM_AREA_LEN(AREA_B))) {
            vm_err_log("new end addr:%d, vm end addr:%d\n", (__SECTOR_ALIGN(new_vm_saddr) + __SECTOR_ALIGN(res_vm_len) +
                __SECTOR_ALIGN(loader_len + sizeof(struct jlfs_file_head))), (VM_AREA_ADR(AREA_A) +
                VM_AREA_LEN(AREA_A) + VM_AREA_LEN(AREA_B)));
            vm_mutex_exit();
            return -2;
        }
    }

    u32 vm_endaddr = VM_AREA_ADR(AREA_B) + VM_AREA_LEN(AREA_B);

    if ((VM_LAST_ADR > VM_AREA_ADR(AREA_B)) && ((VM_LAST_ADR + vm_update_info.reserve_area) > vm_endaddr)) {
        // copy to A
        _vm_area_erase(&vm_obj.area[AREA_A]); // this function will wast a log time to erase vm_erea_using
        vm_data_copy(VM_AREA_ADR(AREA_A), VM_AREA_ADR(AREA_B), VM_LAST_ADR - VM_AREA_ADR(AREA_B));
        u32 addr = VM_AREA_ADR(AREA_B);
        while (addr < VM_LAST_ADR) {
            sfc_erase(SECTOR_ERASER, addr);
            addr += VM_SECTOR_SIZE;
        }

        VM_AREA_USING = AREA_A;
        VM_LAST_ADR = VM_AREA_ADR(AREA_A) + (VM_LAST_ADR - VM_AREA_ADR(AREA_B));
        vm_info_log("last111: 0x%x \n", VM_LAST_ADR);
    }

    u32 last_addr = __SECTOR_ALIGN(VM_LAST_ADR + vm_update_info.reserve_area);

    if (last_addr < vm_endaddr) {
        // copy to end
        u32 src_addr = last_addr;
        u32 dst_addr = vm_endaddr;
        u32 len = 0;
        while (len < res_vm_len) {
            src_addr -= VM_SECTOR_SIZE;
            dst_addr -= VM_SECTOR_SIZE;
            sfc_erase(SECTOR_ERASER, dst_addr);
            u32 rlen = VM_SECTOR_SIZE;
            if ((len + rlen) > res_vm_len) {
                rlen = res_vm_len - len;
            }
            vm_data_copy(dst_addr, src_addr, rlen);
            len += rlen;
        }
    }

    *vm_saddr = vm_endaddr - __SECTOR_ALIGN(res_vm_len);
    *reserve_saddr = vm_endaddr - vm_update_info.reserve_area;

#if 1
    {
        u32 saddr = VM_AREA_ADR(AREA_A);
        u32 eaddr = *vm_saddr;
        while (saddr < eaddr) {
            sfc_erase(SECTOR_ERASER, saddr);
            saddr += VM_SECTOR_SIZE;
        }
    }
#endif

    char *name;
    struct jlfs_file_head head;
    u32 loader_saddr = (*vm_saddr) - (loader_len + sizeof(struct jlfs_file_head));
    loader_saddr = loader_saddr / JLFS_SECTOR_SIZE * JLFS_SECTOR_SIZE;

    head.data_crc = 0xffff;
    head.addr = loader_saddr;
    head.len = loader_len + sizeof(struct jlfs_file_head);
    head.attr = JL_FILE_ATTR_UPDATE;
    head.res = 0xff;
    head.index = 0xffff;
    name = strrchr(vm_update_info.loader_name, '/');
    if (!name) {
        name = vm_update_info.loader_name;
    }
    memcpy_s(head.name, 16, name, 16);

    head.head_crc = CRC16((u8 *)&head + 2, sizeof(struct jlfs_file_head) - 2);

    sfc_write((const u8 *)&head, loader_saddr, sizeof(struct jlfs_file_head));

    vm_mutex_exit();

    return ret;
}

void vm_update_recover(void)
{
    FILE *file = NULL;
    struct vfs_attr attrs;
    if (!vm_update_info.loader_name) {
        return ;
    }
    file = fopen(vm_update_info.loader_name, NULL);
    if (!file) {
        return ;
    }
    int ret = fget_attrs(file, &attrs);
    if (ret) {
        vm_err_log("get attrs err %d \n", ret);
        goto __err_exit;
    }
    vm_info_log("fileaddr:0x%x, filesize:%d \n", attrs.sclust, attrs.fsize);
    u32 vm_endaddr = VM_AREA_ADR(AREA_B) + VM_AREA_LEN(AREA_B);
    u32 vm_file_eaddr = __SECTOR_ALIGN(attrs.sclust + attrs.fsize);

    if ((vm_file_eaddr > VM_AREA_ADR(AREA_A)) && (vm_file_eaddr < vm_endaddr)) {
        u32 res_vm_len;
        u32 src_addr = vm_file_eaddr;
        u32 dst_addr = VM_AREA_ADR(AREA_A);
        u32 len = 0;

        res_vm_len = vm_endaddr - vm_file_eaddr;
        if (res_vm_len > VM_AREA_LEN(AREA_A)) {
            res_vm_len = VM_AREA_LEN(AREA_A);
        }
        if (res_vm_len <= vm_update_info.reserve_area) {
            vm_err_log("res_vm_len:0x%x reserve_area:0x%x \n", res_vm_len, vm_update_info.reserve_area);
            goto __err_exit;
        }
        res_vm_len -= vm_update_info.reserve_area;

        while (len < res_vm_len) {
            sfc_erase(SECTOR_ERASER, dst_addr);
            u32 rlen = VM_SECTOR_SIZE;
            if ((len + rlen) > res_vm_len) {
                rlen = res_vm_len - len;
            }
            vm_data_copy(dst_addr, src_addr, rlen);
            len += rlen;
            src_addr += VM_SECTOR_SIZE;
            dst_addr += VM_SECTOR_SIZE;
        }
        if (dst_addr < vm_file_eaddr) {
        }
        while (dst_addr < vm_endaddr) {
            sfc_erase(SECTOR_ERASER, dst_addr);
            dst_addr += VM_SECTOR_SIZE;
        }
    } else {
        vm_err_log("vm_file_eaddr:0x%x err \n", vm_file_eaddr);
        goto __err_exit;
    }

    fclose(file);
    return ;

__err_exit:
    fclose(file);
    vm_eraser();
}

